import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/*
 * This class serves as the instructions decoder and
 * provides the control signals
 */

public class Instruction extends Constants {

	public static ImplementedOperation opCode;
	public static Registers RS_REGISTER = Registers.$zero;
	public static Registers RT_REGISTER = Registers.$zero;
	public static Registers RD_REGISTER = Registers.$zero;

	public static int Immediate = -1;
	public static int JUMP_ADDRESS = -1;

	public static void clear() {
		RS_REGISTER = Registers.$zero;
		RT_REGISTER = Registers.$zero;
		RD_REGISTER = Registers.$zero;
		Immediate = -1;
		JUMP_ADDRESS = -1;
		clearFlags();
		opCode = ImplementedOperation.NOOP;
	}

	public static void RType() {
		clearFlags();
		RegDst = true;
		RegWrite = true;
		// Bus.startSequence();
	}

	private static void clearFlags() {
		RegDst = false;
		Jump = false;
		Branch = false;
		Branch2 = false;
		MemRead = false;
		MemtoReg = false;
		MemWrite = false;
		ALUSrc = false;
		RegWrite = false;
		JAL = false;
		JR = false;
	}

	private static String displayFlags() {
		String r = "";
		r += "RegDst: " + RegDst + "\n";
		r += "Jump: " + Jump + "\n";
		r += "Branch: " + Branch + "\n";
		r += "MemRead: " + MemRead + "\n";
		r += "MemtoReg: " + MemtoReg + "\n";
		r += "MemWrite: " + MemWrite + "\n";
		r += "ALUSrc: " + ALUSrc + "\n";
		r += "RegWrite: " + RegWrite + "\n";
		r += "JAL: " + JAL + "\n";
		r += "JR: " + JR + "\n";
		return r;
	}

	public static void JType() {
		clearFlags();
		Jump = true;
		switch (opCode) {
		case JAL:
			RegWrite = true;
			JAL = true;
			break;
		case JR:
			JR = true;
			break;
		default:
			break;
		}
		// Bus.startSequence();
	}

	public static void IType() {
		clearFlags();
		switch (opCode) {
		case LW:
			MemRead = true;
			MemtoReg = true;
			break;
		case SW:
			MemWrite = true;
			break;
		case BEQ:
			Branch = true;
			break;
		case BNE:
			Branch2 = true;
			break;
		default:
			ALUSrc = true;
			RegWrite = true;
			break;
		}

		// Bus.startSequence();

	}

	public static void set(String instructionStr) {
		// TODO Auto-generated method stub
		// String rTypeRegEx =
		// "(\\$[a-zA-Z0-9]+)\\s*,\\s*(\\$[a-zA-Z0-9]+)\\s*,\\s*(\\$[a-zA-Z0-9]+)";
		// String iTypeRegEx =
		// "(\\$[a-zA-Z0-9]+)\\s*,\\s*(?:(\\$[a-zA-Z0-9]+)\\s*,\\s*(-?[0-9]+)$)|(?:(-?[0-9]+)\\((\\$[a-zA-Z0-9]+)\\)$)|(?:(\\$[a-zA-Z0-9]+)\\s*,\\s*0x(\\d+)$)";

		instructionStr = instructionStr.replace(',', ' ');
		StringTokenizer in = new StringTokenizer(instructionStr);

		Matcher x = Pattern.compile("^([A-Za-z]+).*").matcher(instructionStr);
		x.find();
		opCode = ImplementedOperation.valueOf(x.group(1).toUpperCase());

		String temp = in.nextToken();
		String[] temp2;
		switch (opCode) {
		case ADD:
			RType();
			opCode = ImplementedOperation.ADD;
			ALUOperation = ALUOp.ADD;
			Immediate = 0;
			temp = in.nextToken();
			RD_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RS_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RT_REGISTER = Registers.valueOf(temp.toLowerCase());
			break;
		case SUB:
			RType();
			opCode = ImplementedOperation.SUB;
			ALUOperation = ALUOp.SUB;
			Immediate = 0;
			temp = in.nextToken();
			RD_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RS_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RT_REGISTER = Registers.valueOf(temp.toLowerCase());
			break;
		case SLL:
			RType();
			opCode = ImplementedOperation.SLL;
			ALUOperation = ALUOp.SLL;
			Immediate = 0;
			temp = in.nextToken();
			RD_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RS_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RT_REGISTER = Registers.valueOf(temp.toLowerCase());
			break;
		case SRL:
			RType();
			opCode = ImplementedOperation.SRL;
			ALUOperation = ALUOp.SRL;
			Immediate = 0;
			temp = in.nextToken();
			RD_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RS_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RT_REGISTER = Registers.valueOf(temp.toLowerCase());
			break;
		case NOR:
			RType();
			opCode = ImplementedOperation.NOR;
			ALUOperation = ALUOp.NOR;
			Immediate = 0;
			temp = in.nextToken();
			RD_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RS_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RT_REGISTER = Registers.valueOf(temp.toLowerCase());
			break;
		case AND:
			RType();
			opCode = ImplementedOperation.AND;
			ALUOperation = ALUOp.AND;
			Immediate = 0;
			temp = in.nextToken();
			RD_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RS_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RT_REGISTER = Registers.valueOf(temp.toLowerCase());
			break;
		case OR:
			RType();
			opCode = ImplementedOperation.OR;
			ALUOperation = ALUOp.OR;
			Immediate = 0;
			temp = in.nextToken();
			RD_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RS_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RT_REGISTER = Registers.valueOf(temp.toLowerCase());
			break;
		case SLT:
			RType();
			opCode = ImplementedOperation.SLT;
			ALUOperation = ALUOp.SUB;
			Immediate = 0;
			temp = in.nextToken();
			RD_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RS_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RT_REGISTER = Registers.valueOf(temp.toLowerCase());
			break;
		case SLTU:
			RType();
			opCode = ImplementedOperation.SLTU;
			ALUOperation = ALUOp.SUB;
			Immediate = 0;
			temp = in.nextToken();
			RD_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RS_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RT_REGISTER = Registers.valueOf(temp.toLowerCase());
			break;

		case ADDI:
			IType();
			opCode = ImplementedOperation.ADDI;
			ALUOperation = ALUOp.ADD;

			temp = in.nextToken();
			RT_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RS_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			Immediate = Integer.parseInt(temp);

			break;
		case LW:
			IType();
			opCode = ImplementedOperation.LW;
			ALUOperation = ALUOp.ADD;

			temp = in.nextToken();
			RT_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			temp2 = temp.split("(");
			Immediate = Integer.parseInt(temp2[0]);
			RS_REGISTER = Registers.valueOf(temp2[1].toLowerCase());
			break;
		case SW:
			IType();
			opCode = ImplementedOperation.SW;
			ALUOperation = ALUOp.ADD;

			temp = in.nextToken();
			RT_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			temp2 = temp.split("(");
			Immediate = Integer.parseInt(temp2[0]);
			RS_REGISTER = Registers.valueOf(temp2[1].toLowerCase());
			break;
		case SLTUI:
			IType();
			opCode = ImplementedOperation.SLTUI;
			ALUOperation = ALUOp.ADD;

			temp = in.nextToken();
			RT_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RS_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			Immediate = Integer.parseInt(temp);
			break;
		case SLTI:
			IType();
			opCode = ImplementedOperation.SLTI;
			ALUOperation = ALUOp.ADD;

			temp = in.nextToken();
			RT_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RS_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			Immediate = Integer.parseInt(temp);
			break;
		case ANDI:
			IType();
			opCode = ImplementedOperation.ANDI;
			ALUOperation = ALUOp.AND;

			temp = in.nextToken();
			RT_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RS_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			Immediate = Integer.parseInt(temp);

			break;
		case ORI:
			IType();
			opCode = ImplementedOperation.ORI;
			ALUOperation = ALUOp.ADD;

			temp = in.nextToken();
			RT_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RS_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			Immediate = Integer.parseInt(temp);

			break;
		case BEQ:
			IType();
			opCode = ImplementedOperation.BEQ;
			ALUOperation = ALUOp.SUB;

			temp = in.nextToken();
			RT_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RS_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			Immediate = Bus.getValidBranchAddress(PC.currentAddress, Integer.parseInt(temp));
			break;
		case BNE:
			IType();
			opCode = ImplementedOperation.BNE;
			ALUOperation = ALUOp.SUB;

			temp = in.nextToken();
			RT_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			RS_REGISTER = Registers.valueOf(temp.toLowerCase());
			temp = in.nextToken();
			Immediate = Bus.getValidBranchAddress(PC.currentAddress, Integer.parseInt(temp));

			break;
		case J:
			JType();
			opCode = ImplementedOperation.J;
			ALUOperation = ALUOp.ADD;

			temp = in.nextToken();
			JUMP_ADDRESS = Bus.getValidJumpAddress(PC.currentAddress, Integer.parseInt(temp));
			break;
		case JAL:
			JType();
			opCode = ImplementedOperation.JAL;
			ALUOperation = ALUOp.ADD;

			temp = in.nextToken();
			JUMP_ADDRESS = Bus.getValidJumpAddress(PC.currentAddress, Integer.parseInt(temp));
			break;
		case JR:
			JType();
			opCode = ImplementedOperation.JR;
			ALUOperation = ALUOp.ADD;

			temp = in.nextToken();
			RD_REGISTER = Registers.valueOf(temp.toLowerCase());
			break;
		default:
			System.err.println("No Such Instruction: " + opCode);
			break;
		}
	}
/*
	public static void main(String[] args) {
		String[] instructions = { "ADD $t1, $t2, $zero", "ADDI $t5, $t7, -5",
				"ADDI $t2, $t4, 8", "beq $t5, $t7, 9239", "j 1234",
				"JAL 123456", "JR $ra" };
		for (int i = 0; i < instructions.length; i++) {
			clear();
			System.out.println(instructions[i]);
			set(instructions[i]);
			System.out.println(Display());
		}

	}

	// */
	public static String Display() {
		String r = "";
		r += "OP CODE: %s\n";
		r += "RD Register: %s\n";
		r += "RT Register: %s\n";
		r += "RS Register: %s\n";
		r += "ALU OP: %s\n";
		r += "Immediate: %s \n";
		r += "Jump_Address: %s";
		return String.format(r, opCode, RD_REGISTER, RT_REGISTER, RS_REGISTER,
				ALUOperation, Immediate, JUMP_ADDRESS)
				+ "\nFlags:\n"
				+ displayFlags();
	}
}
